#include "fusionlayers.h"
#include <limits.h>
using namespace std;

//************************************************************* Constructor

FusionLayers::FusionLayers(e_fusion state, int widthImage, int heightImage){
    xPosFusion = 0;
    yPosFusion = 0;
    xEndFusion = 0;
    yEndFusion = 0;

    this->state = state;

    this->widthImage = widthImage;
    this->heightImage = heightImage;
}

FusionLayers::FusionLayers(int widthImage, int heightImage){
    xPosFusion = 0;
    yPosFusion = 0;
    xEndFusion = 0;
    yEndFusion = 0;

    this->widthImage = widthImage;
    this->heightImage = heightImage;
}
//************************************************************* Fusion

Layer* FusionLayers::fusion(list<Layer*> listLayerToFusion){

    switch (state){
        case ResizeToUnionLayers :

            fusionnerUnion(listLayerToFusion);

            break;
        case CropToIntersectionLayers :

            fusionnerIntersection(listLayerToFusion);
            break;
        default : //case SizeImage :

            fusionnerFitSizeImage(listLayerToFusion);
            break;
    };
    return layerFusion;
}

//************************************************************* Type Fusion

Layer* FusionLayers::fusionnerUnion(list<Layer*> listLayerToFusion){

    if(listLayerToFusion.size() <= 1){
        return listLayerToFusion.back();
    }

    int xPosFusion = INT_MAX;
    int yPosFusion = INT_MAX;
    int xEndFusion = INT_MIN;
    int yEndFusion = INT_MIN;

    int numLayerToFusion = listLayerToFusion.size();

    // Search of the border of the new layer create to represente the fusion
    for(int i = 0; i < numLayerToFusion; i++){
        Layer* tmpLayer = listLayerToFusion.back();
        listLayerToFusion.pop_back();
        listLayerToFusion.push_front(tmpLayer);

        xPosFusion = min(xPosFusion, (int) tmpLayer->x());
        yPosFusion = min(yPosFusion, (int) tmpLayer->y());
        xEndFusion = max(xEndFusion, (int) tmpLayer->getWidth() + (int) tmpLayer->x());
        yEndFusion = max(yEndFusion, (int) tmpLayer->getHeight() + (int) tmpLayer->y());
    }

    // If the intersection is empty we do nothing
    if(xEndFusion - xPosFusion <= 0 || yEndFusion - yPosFusion <= 0){
        return NULL;
    }

    // Creation of the new layer representing the fusion of the other layer
    layerFusion = new Layer(xPosFusion, yPosFusion, xEndFusion - xPosFusion, yEndFusion - yPosFusion, widthImage, heightImage);
    layerFusion->setOpacity(1.0);
    // The fusion will be place at the position of the lowest layer to fusion
    layerFusion->setZValue(listLayerToFusion.back()->zValue());

    QImage* imageFusion = layerFusion->getImage();
    while(!listLayerToFusion.empty()){
        Layer* tmpLayer = listLayerToFusion.back();
        listLayerToFusion.pop_back();

        QImage* imgTmp = tmpLayer->getImage();

        // Represent the position where we could start and end to take the value on the source image (= an image of a layer to fusion)
        int xBeginDrawingSource, yBeginDrawingSource, xEndDrawingSource, yEndDrawingSource;
        // Point representing the position to start to draw on the image of the layerFusion
        int xBeginDrawingFusion, yBeginDrawingFusion;

        xBeginDrawingSource = 0;
        yBeginDrawingSource = 0;
        xEndDrawingSource = tmpLayer->getWidth();
        yEndDrawingSource = tmpLayer->getHeight();
        xBeginDrawingFusion = tmpLayer->x() - layerFusion->x();
        yBeginDrawingFusion = tmpLayer->y() - layerFusion->y();

        // If the layer is out of the boundary of the fusion layer we must do nothing
        if(!(tmpLayer->x() > (layerFusion->x() + layerFusion->getWidth()) ||
           tmpLayer->y() > (layerFusion->y() + layerFusion->getHeight()) ||
           tmpLayer->x() + tmpLayer->getWidth() < layerFusion->x() ||
           tmpLayer->y() + tmpLayer->getHeight() < layerFusion->y()))
        {

            for(int i = xBeginDrawingSource; i < xEndDrawingSource; i++){
                for(int j = yBeginDrawingSource; j < yEndDrawingSource; j++){
                    QRgb rgbTmp = imgTmp->pixel(i, j);
                    QRgb rgbFusion = imageFusion->pixel(i + xBeginDrawingFusion,j + yBeginDrawingFusion);

                    double alphaTmp = (double) qAlpha(rgbTmp) * (double) tmpLayer->opacity();
                    double alphaFusion = (double) qAlpha(rgbFusion) * (double) layerFusion->opacity();

                    int blue = (256.0 - alphaTmp)* qBlue(rgbFusion) / 256.0 + alphaTmp * qBlue(rgbTmp) / 256.0;
                    int red = (256.0 - alphaTmp)* qRed(rgbFusion) / 256.0 + alphaTmp * qRed(rgbTmp) / 256.0;
                    int green = (256.0 - alphaTmp)* qGreen(rgbFusion) / 256.0 + alphaTmp * qGreen(rgbTmp) / 256.0;

                    double alpha = (1.0 - (alphaTmp / 256.0)) * (1.0 - (alphaFusion / 256.0));
                    alpha = 1.0 - alpha;
                    alpha = alpha * 256.0;

                    imageFusion->setPixel(i + xBeginDrawingFusion,j + yBeginDrawingFusion,qRgba(red, green, blue, alpha));
                }
            }
        }
    }
    return layerFusion;
}

Layer* FusionLayers::fusionnerFitSizeImage(list<Layer*> listLayerToFusion){

    if(listLayerToFusion.size() == 0){
        return NULL;
    }

    xPosFusion = 0;
    yPosFusion = 0;
    xEndFusion = widthImage;
    yEndFusion = heightImage;

    // Creation of the new layer representing the fusion of the other layer
    layerFusion = new Layer(xPosFusion, yPosFusion, xEndFusion - xPosFusion, yEndFusion - yPosFusion, widthImage, heightImage);

    // The fusion will be place at the position of the lowest layer to fusion
    layerFusion->setZValue(listLayerToFusion.back()->zValue());

    QImage* imageFusion = layerFusion->getImage();

    while(!listLayerToFusion.empty()){
        Layer* tmpLayer = listLayerToFusion.back();
        listLayerToFusion.pop_back();

        QImage* imgTmp = tmpLayer->getImage();

        // Draw in the new layer
        int xBeginDrawingSource, yBeginDrawingSource, xEndDrawingSource, yEndDrawingSource;
        int xBeginDrawingFusion, yBeginDrawingFusion;

        if(tmpLayer->x() - xPosFusion < 0){
            xBeginDrawingSource = xPosFusion - tmpLayer->x();
        }else{
            xBeginDrawingSource = 0;
        }
        if(tmpLayer->y() - yPosFusion < 0){
            yBeginDrawingSource = yPosFusion - tmpLayer->y();
        }else{
            yBeginDrawingSource = 0;
        }

        xEndDrawingSource = min(xEndFusion, (int) tmpLayer->x() + tmpLayer->getWidth()) - max((int) tmpLayer->x(), xPosFusion) + xBeginDrawingSource;

        yEndDrawingSource = min(yEndFusion, (int) tmpLayer->y() + tmpLayer->getHeight()) - max((int) tmpLayer->y(), yPosFusion) + yBeginDrawingSource;

        xBeginDrawingFusion = max(xPosFusion, (int) tmpLayer->x());/* tmpLayer->x() - layerFusion->x();*/
        yBeginDrawingFusion = max(yPosFusion, (int) tmpLayer->y());/*tmpLayer->y() - layerFusion->y();*/

        // iIf the layer is out of the boundary of the fusion layer we must do nothing
        if(!(tmpLayer->x() > (layerFusion->x() + layerFusion->getWidth()) ||
           tmpLayer->y() > (layerFusion->y() + layerFusion->getHeight()) ||
           tmpLayer->x() + tmpLayer->getWidth() < layerFusion->x() ||
           tmpLayer->y() + tmpLayer->getHeight() < layerFusion->y()))
        {
            for(int i = xBeginDrawingSource; i < xEndDrawingSource; i++){
                for(int j = yBeginDrawingSource; j < yEndDrawingSource; j++){
                    QRgb rgbTmp = imgTmp->pixel(i, j);
                    QRgb rgbFusion = imageFusion->pixel(i - xBeginDrawingSource + xBeginDrawingFusion,
                                                        j - yBeginDrawingSource + yBeginDrawingFusion);
                    double alphaTmp = (double) qAlpha(rgbTmp) * (double) tmpLayer->opacity();
                    double alphaFusion = (double) qAlpha(rgbFusion) * (double) layerFusion->opacity();

                    int blue = (256.0 - alphaTmp)* qBlue(rgbFusion) / 256.0 + alphaTmp * qBlue(rgbTmp) / 256.0;
                    int red = (256.0 - alphaTmp)* qRed(rgbFusion) / 256.0 + alphaTmp * qRed(rgbTmp) / 256.0;
                    int green = (256.0 - alphaTmp)* qGreen(rgbFusion) / 256.0 + alphaTmp * qGreen(rgbTmp) / 256.0;

                    double alpha = (1.0 - (alphaTmp / 256.0)) * (1.0 - (alphaFusion / 256.0));
                    alpha = 1.0 - alpha;
                    alpha = alpha * 256.0;

                    imageFusion->setPixel(i - xBeginDrawingSource + xBeginDrawingFusion, j - yBeginDrawingSource + yBeginDrawingFusion, qRgba(red, green, blue, alpha));
                }
            }
        }
    }
    return layerFusion;
}

Layer* FusionLayers::fusionnerIntersection(list<Layer*> listLayerToFusion){

    if(listLayerToFusion.size() == 0){
        return NULL;
    }

    xPosFusion = INT_MIN;
    yPosFusion = INT_MIN;
    xEndFusion = INT_MAX;
    yEndFusion = INT_MAX;

    // Schearch of the border of the new layer create to represente the fusion
    int numLayerToFusion = listLayerToFusion.size();
    for(int i = 0; i < numLayerToFusion; i++){
        Layer* tmpLayer = listLayerToFusion.back();
        listLayerToFusion.pop_back();
        listLayerToFusion.push_front(tmpLayer);

        xPosFusion = max(xPosFusion, (int) tmpLayer->x());
        yPosFusion = max(yPosFusion, (int) tmpLayer->y());
        xEndFusion = min(xEndFusion, (int) tmpLayer->getWidth() + (int) tmpLayer->x());
        yEndFusion = min(yEndFusion, (int) tmpLayer->getHeight() + (int) tmpLayer->y());
    }

    // If the intersection is empty we do nothing
    if(xEndFusion - xPosFusion <= 0 || yEndFusion - yPosFusion <= 0){
        return NULL;
    }

    // Creation of the new layer representing the fusion of the other layer
    layerFusion = new Layer(xPosFusion, yPosFusion, xEndFusion - xPosFusion, yEndFusion - yPosFusion, widthImage, heightImage);

    // The fusion will be place at the position of the lowest layer to fusion
    layerFusion->setZValue(listLayerToFusion.back()->zValue());

    QImage* imageFusion = layerFusion->getImage();
    while(!listLayerToFusion.empty()){
        Layer* tmpLayer = listLayerToFusion.back();
        listLayerToFusion.pop_back();

        QImage* imgTmp = tmpLayer->getImage();

        // Draw in the new layer
        int xBeginDrawingSource, yBeginDrawingSource, xEndDrawingSource, yEndDrawingSource;
        int xBeginDrawingFusion, yBeginDrawingFusion;

        xBeginDrawingSource = xPosFusion - tmpLayer->x();
        yBeginDrawingSource = yPosFusion - tmpLayer->y();
        xEndDrawingSource = xEndFusion - tmpLayer->x();
        yEndDrawingSource = yEndFusion - tmpLayer->y();
        xBeginDrawingFusion = xPosFusion - layerFusion->x();
        yBeginDrawingFusion = yPosFusion - layerFusion->y();

        // If the layer is out of the boundary of the fusion layer we must do nothing
        if(!(tmpLayer->x() > (layerFusion->x() + layerFusion->getWidth()) ||
           tmpLayer->y() > (layerFusion->y() + layerFusion->getHeight()) ||
           tmpLayer->x() + tmpLayer->getWidth() < layerFusion->x() ||
           tmpLayer->y() + tmpLayer->getHeight() < layerFusion->y()))
        {
            for(int i = xBeginDrawingSource; i < xEndDrawingSource; i++){
                for(int j = yBeginDrawingSource; j < yEndDrawingSource; j++){
                    QRgb rgbTmp = imgTmp->pixel(i, j);
                    QRgb rgbFusion = imageFusion->pixel(i - xBeginDrawingSource + xBeginDrawingFusion,
                                                        j - yBeginDrawingSource + yBeginDrawingFusion);
                                        double alphaTmp = (double) qAlpha(rgbTmp) * (double) tmpLayer->opacity();
                    double alphaFusion = (double) qAlpha(rgbFusion) * (double) layerFusion->opacity();

                    int blue = (256.0 - alphaTmp)* qBlue(rgbFusion) / 256.0 + alphaTmp * qBlue(rgbTmp) / 256.0;
                    int red = (256.0 - alphaTmp)* qRed(rgbFusion) / 256.0 + alphaTmp * qRed(rgbTmp) / 256.0;
                    int green = (256.0 - alphaTmp)* qGreen(rgbFusion) / 256.0 + alphaTmp * qGreen(rgbTmp) / 256.0;

                    double alpha = (1.0 - (alphaTmp / 256.0)) * (1.0 - (alphaFusion / 256.0));
                    alpha = 1.0 - alpha;
                    alpha = alpha * 256.0;

                    imageFusion->setPixel(i - xBeginDrawingSource + xBeginDrawingFusion, j - yBeginDrawingSource + yBeginDrawingFusion, qRgba(red, green, blue, alpha));
                }
            }
        }
    }
    return layerFusion;
}
void FusionLayers::fusionnerFitToLayer(QImage* image, Layer* layerFusion,int xImage, int yImage){


    int xPosFusion = layerFusion->x();
    int yPosFusion = layerFusion->y();
    int xEndFusion = layerFusion->x()+layerFusion->getWidth();
    int yEndFusion = layerFusion->y()+layerFusion->getHeight();

    QImage* imageFusion = layerFusion->getImage();


    // Represent the position where we could start and end to take the value on the source image (= an image of a layer to fusion)
    int xBeginDrawingSource, yBeginDrawingSource, xEndDrawingSource, yEndDrawingSource;
    // Point representing the position to start to draw on the image of the layerFusion
    int xBeginDrawingFusion, yBeginDrawingFusion;


    xBeginDrawingSource = max(xImage,xPosFusion)-xImage;
    yBeginDrawingSource = max(yImage,yPosFusion)-yImage;
    xEndDrawingSource = min(xEndFusion,xImage+image->width()) - xImage;
    yEndDrawingSource = min(yEndFusion,yImage+image->height()) - yImage;

    xBeginDrawingFusion = max(xImage,xPosFusion) - xPosFusion -xBeginDrawingSource;
    yBeginDrawingFusion = max(yImage,yPosFusion) - yPosFusion -yBeginDrawingSource;


    // If the layer is out of the boundary of the fusion layer we must do nothing
    if(!(xImage > (layerFusion->x() + layerFusion->getWidth()) ||
         yImage > (layerFusion->y() + layerFusion->getHeight()) ||
         xImage + image->width() < layerFusion->x() ||
         yImage + image->height() < layerFusion->y()))
    {

        for(int i = xBeginDrawingSource; i < xEndDrawingSource; i++){
            for(int j = yBeginDrawingSource; j < yEndDrawingSource; j++){
                QRgb rgbTmp = image->pixel(i, j);
                QRgb rgbFusion = imageFusion->pixel(i + xBeginDrawingFusion,j + yBeginDrawingFusion);

                double alphaTmp = (double) qAlpha(rgbTmp);
                double alphaFusion = (double) qAlpha(rgbFusion) * (double) layerFusion->opacity();

                int blue = (256.0 - alphaTmp)* qBlue(rgbFusion) / 256.0 + alphaTmp * qBlue(rgbTmp) / 256.0;
                int red = (256.0 - alphaTmp)* qRed(rgbFusion) / 256.0 + alphaTmp * qRed(rgbTmp) / 256.0;
                int green = (256.0 - alphaTmp)* qGreen(rgbFusion) / 256.0 + alphaTmp * qGreen(rgbTmp) / 256.0;

                double alpha = (1.0 - (alphaTmp / 256.0)) * (1.0 - (alphaFusion / 256.0));
                alpha = 1.0 - alpha;
                alpha = alpha * 256.0;

                imageFusion->setPixel(i + xBeginDrawingFusion,j + yBeginDrawingFusion,qRgba(red, green, blue, alpha));
            }
        }
    }
}
